package com.demarc.pesquisas.infraestrutura.excecao;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.faces.FacesException;
import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerWrapper;
import javax.faces.event.ExceptionQueuedEvent;
import javax.faces.event.ExceptionQueuedEventContext;

import org.apache.log4j.Logger;
import org.hibernate.exception.ConstraintViolationException;

import com.demarc.pesquisas.infraestrutura.mensagem.MensagemJsfUtils;
import com.demarc.pesquisas.infraestrutura.mensagem.MensagemUtil;
import com.demarc.pesquisas.negocio.util.ConstantesMensagens;

public class ApplicationExceptionHandler extends ExceptionHandlerWrapper {

	/** Códigos de erros Constraint lançados pelo banco de dados DB2 */
	private static final int CODIGO_ERRO_EXCLUSAO = -532;
	private static final int CODIGO_ERRO_INCLUSAO_ALTERACAO = -407;
	private static final int CODIGO_ERRO_ALTERACAO_EXCLUSAO = -803;

	private final ExceptionHandler wrappedException;

	private static final Logger LOGGER = Logger
			.getLogger(ExceptionHandler.class);

	public ApplicationExceptionHandler(ExceptionHandler exception) {
		wrappedException = exception;
	}

	@Override
	public ExceptionHandler getWrapped() {
		return wrappedException;
	}

	@Override
	public void handle() throws FacesException {
		List<ExceptionQueuedEvent> events = getEvents();

		try {
			for (ExceptionQueuedEvent event : events) {
				if (event != null && event.getContext() != null) {
					ExceptionQueuedEventContext context = event.getContext();

					Throwable exception = context.getException();
					NegocioException negocioException = getNegocioException(exception);

					if (isStaleObjectStateException(exception)
							|| isConstraintViolationException(exception,
									CODIGO_ERRO_ALTERACAO_EXCLUSAO)) {

						String mensagem = MensagemUtil
								.get(ConstantesMensagens.GERAL_ACESSO_CONCORRENTE);
						LOGGER.error(mensagem);
						MensagemJsfUtils.adicionarMensagemErro(mensagem);

					} else if (isConstraintViolationException(exception,
							CODIGO_ERRO_EXCLUSAO)) {

						String mensagem = MensagemUtil
								.get(ConstantesMensagens.GERAL_EXCLUSAO_UTILIZADA_SISTEMA);
						LOGGER.error(mensagem);
						MensagemJsfUtils.adicionarMensagemErro(mensagem);

					} else if (isConstraintViolationException(exception,
							CODIGO_ERRO_INCLUSAO_ALTERACAO)) {

						String mensagem = MensagemUtil
								.get(ConstantesMensagens.GERAL_INCLUSAO_ATUALIZACAO_CAMPO_OBRIGATORIO_NO_BANCO);
						LOGGER.error(mensagem);
						MensagemJsfUtils.adicionarMensagemErro(mensagem);

					} else if (negocioException == null) {

						String mensagem = exception.getCause() == null ? exception
								.getMessage() : exception.getCause()
								.getMessage();

						exception.printStackTrace(); // NOPMD
						LOGGER.error(mensagem);
						MensagemJsfUtils
								.adicionarMensagemErro("Erro inesperado!");

					} else {
						LOGGER.error(negocioException.getMessage());
						MensagemJsfUtils
								.adicionarMensagemErro(negocioException);
					}
				}
			}
		} catch (Exception e) {
			LOGGER.error(e);
		} finally {
			try {
				events.clear();
			} catch (Exception e) {
				LOGGER.error(e);
			}
		}
		try {
			super.handle();
		} catch (Exception e) {
			LOGGER.error(e);
		}
	}

	private List<ExceptionQueuedEvent> getEvents() {
		List<ExceptionQueuedEvent> result;
		Iterable<ExceptionQueuedEvent> events = getUnhandledExceptionQueuedEvents();

		if (events instanceof List) {
			result = (List<ExceptionQueuedEvent>) events;
		} else {
			result = new ArrayList<ExceptionQueuedEvent>();
		}
		return result;
	}

	private boolean isConstraintViolationException(Throwable exception,
			int codigoErro) {
		boolean resulto = false;

		if (exception != null) {
			if (exception instanceof ConstraintViolationException) {
				SQLException sqlException = ((ConstraintViolationException) exception)
						.getSQLException();
				if (codigoErro == sqlException.getErrorCode()) {
					resulto = true;
				}
			} else {
				resulto = isConstraintViolationException(exception.getCause(),
						codigoErro);
			}
		}

		return resulto;
	}

	private NegocioException getNegocioException(Throwable exception) {
		if (exception == null) {
			return null;
		}

		if (exception instanceof NegocioException) {
			return (NegocioException) exception;
		} else {
			return getNegocioException(exception.getCause());
		}
	}

	private boolean isStaleObjectStateException(Throwable e) {
		boolean resulto = false;

		if (e != null) {
			if (e instanceof org.hibernate.StaleObjectStateException) {
				resulto = true;
			} else {
				resulto = isStaleObjectStateException(e.getCause());
			}
		}
		return resulto;
	}
}
